package main

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	pkgMysql "github.com/go-sql-driver/mysql"
	imapp "go.manyji.com/im3/app"
	"go.manyji.com/im3/crypto"
	"go.manyji.com/lib/app"
	"go.manyji.com/lib/config"
	"go.manyji.com/lib/hash"
	"go.manyji.com/lib/log"
	"go.manyji.com/lib/mysql"
	"go.manyji.com/lib/rand"
	"net/http"
	"net/url"
	"sieve_agent_server/lib/server"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

var (
	sessLock         = sync.Mutex{}
	sessions         = map[uint64]*UserAuth{}
	fSessExpire      = config.Int("session.expire", 3600, "session过期时间 秒")
	fCookieName      = config.String("session.cookie", "session", "session cookie name")
	roleInitFinished = &atomic.Bool{}
)

var (
	adminAccount = User{
		ID:      1,
		Account: "admin",
	}
	adminRole = &Role{
		Name:  "管理员",
		Alias: "admin",
	}

	salespersonRole = &Role{
		Name:  "业务员",
		Alias: "salesperson",
	}

	agentRole = &Role{
		Name:  "代理商",
		Alias: "agent",
	}

	channelAgentRole = &Role{
		Name:  "渠道主",
		Alias: "channelAgent",
	}
)

func init() {

	app.Async(func() {
		WaitInit()

		if err := (&data_User{}).loadRole(); err != nil {
			panic(err)
		}
	})

	app.Async(func() {
		WaitInit()

		d := &data_User{}
		server.Register(GetLoginUid)
		if err := d.loadSessions(); err != nil {
			log.Fatal(err)
		}
		for {
			time.Sleep(time.Second)
			d.delExpireSession()
		}
	})
}

func WaitRoleInit() {
	for !roleInitFinished.Load() {
		time.Sleep(time.Millisecond * 10)
	}
}

func Login(w http.ResponseWriter, r *http.Request) {

	w.Header().Set("Access-Control-Allow-Origin", "*")

	if err := r.ParseForm(); err != nil {
		w.WriteHeader(http.StatusBadRequest)
		_, _ = w.Write([]byte(err.Error()))
		return
	}
	args := r.PostForm
	c := &server.Context{
		Args: url.Values{},
		Http: &server.ContextHttp{
			ResponseWriter: w,
			Request:        r,
		},
	}

	ret := (&data_User{}).Login(context.Background(), c, args.Get("u"), args.Get("p"))

	bt, _ := json.Marshal(ret)
	_, _ = w.Write(bt)
}

func Logout(w http.ResponseWriter, r *http.Request) {

	w.Header().Set("Access-Control-Allow-Origin", "*")

	c := &server.Context{
		Args: url.Values{},
		Http: &server.ContextHttp{
			ResponseWriter: w,
			Request:        r,
		},
	}

	uid := GetLoginUid(r)
	if uid != 0 {
		(&data_User{}).Logout(context.Background(), c, uint64(uid))
		http.SetCookie(w, &http.Cookie{
			Name:   "app" + strconv.Itoa(imapp.AppId()) + "_" + *fCookieName,
			MaxAge: -1,
			Path:   *fPathRoot,
		})
	}

	http.Redirect(w, r, *fPathRoot, http.StatusFound)
	server.HandleIndex(w, r)
	return
}

func (a *data_User) loadRole() error {

	ctx := context.Background()

	if _id, err := a.LoadRole(ctx, 1); err != nil {
		log.Err("初始化加载管理员角色失败：%+v", err)
	} else {
		adminAccount.RoleId = uint64(_id)
	}
	if _, err := a.LoadRole(ctx, 2); err != nil {
		log.Err("初始化加载业务员角色失败：%+v", err)
	}
	if _, err := a.LoadRole(ctx, 3); err != nil {
		log.Err("初始化加载代理商角色失败：%+v", err)
	}
	if _, err := a.LoadRole(ctx, 4); err != nil {
		log.Err("初始化加载渠道主角色失败：%+v", err)
	}

	roleInitFinished.Store(true)
	return nil
}

func isLockError(err error) bool {
	e, ok := err.(*pkgMysql.MySQLError)
	if !ok {
		return false
	}

	return e.Number == 1205 || e.Number == 1213
}

// 判断是否是超时错误
func isDeadlineExceededError(err error) bool {

	if err == (context.DeadlineExceeded) {
		return true
	}

	return false
}

func (a *data_User) Logout(ctx context.Context, c *server.Context, uid uint64) interface{} {

	var (
		logName = "退出日志"
		logMsg  = "退出成功"
		userId  = uid
	)

	//a.delExpireSession(uid)
	go func() {
		resp, err := a.GetUserInfoByUID(ctx, userId, false)
		if err != nil {
			log.Err(fmt.Sprintf("获取用户信息失败:%+v", err))
			return
		}

		if resp == nil {
			log.Err(fmt.Sprintf("不存在的用户:%+v", userId))
			return
		}

		log.Info(fmt.Sprintf("%+v", a.loginRecord(ctx, c, userId, resp.Account, logName, logMsg)))
	}()
	return nil
}

func (a *data_User) GetByName(ctx context.Context, name string) (*User, error) {

	var (
		querySQL = fmt.Sprintf(`
	SELECT * FROM user 
	WHERE account = '%v'
	AND deleted = 0
`, name)
	)

	req, err := db.QueryRaw(ctx, querySQL, User{})
	if err != nil {
		return nil, err
	}

	if req == nil {
		return nil, errors.New(fmt.Sprintf("record not  found"))
	}
	return req[0].(*User), nil
}

func (a *data_User) Login(ctx context.Context, c *server.Context, userName, password string) interface{} {

	// 成功返回token，不成功返回空
	var (
		//		querySQL = fmt.Sprintf(`
		//			SELECT
		//				*
		//			FROM user
		//			WHERE account = '%v' AND passwd = '%v' AND deleted = 0 ORDER BY create_at DESC LIMIT 1
		//`, userName, password)

		querySQL = fmt.Sprintf(`
			SELECT 
				*
			FROM user
			WHERE account = '%v' AND deleted = 0 ORDER BY create_at DESC LIMIT 1
`, userName)
	)

	resp, err := db.QueryRaw(ctx, querySQL, User{})
	if err != nil {
		return err
	}

	var (
		respUser *User
		logName  = "登录日志"
		logMsg   = "登录成功"
	)

	defer func() {
		go func() {
			if respUser != nil {
				userId := respUser.ID
				log.Info("%+v", a.loginRecord(ctx, c, userId, userName, logName, logMsg))
				log.Info("%+v", a.updateLastLogin(ctx, userId, time.Now().Unix()))
			}
		}()
	}()

	if len(resp) <= 0 {
		msg := "账号密码错误"
		logName, logMsg = "登录失败日志", msg
		return map[string]interface{}{
			"cookie": "",
			"msg":    "账号密码错误",
		}
	}

	respUser = resp[0].(*User)
	pwdHash := respUser.Passwd
	if len(pwdHash) <= 64 {
		return map[string]interface{}{
			"cookie": "",
			"msg":    "密码错误",
		}
	}

	if hash.SHA256([]byte(password+pwdHash[64:])) != pwdHash[:64] {
		return map[string]interface{}{
			"cookie": "",
			"msg":    "密码错误",
		}
	}

	if respUser.Status == 2 {
		msg := "账户被冻结"
		logName, logMsg = "登录失败日志", msg
		return map[string]interface{}{
			"cookie": "",
			"msg":    "账户被冻结，不可登录。如需解冻，请联系上级",
		}
	}

	if !(&data_Role{}).IsAgentRole(respUser.RoleId) {
		msg := "用户不存在"
		logName, logMsg = "登录失败日志", msg
		return map[string]interface{}{
			"cookie": "",
			"msg":    "用户不存在",
		}
	}

	_cookie, err := a.createLoginToken(ctx, respUser.ID, c.Http.ResponseWriter)
	if err != nil {
		return map[string]interface{}{
			"cookie": _cookie,
			"msg":    "服务器错误",
		}
	}

	return map[string]interface{}{
		"cookie": _cookie,
		"msg":    "登录成功",
	}
}

func (a *data_User) updateLastRecharge(ctx context.Context, uid uint64, lastRecharge int64) error {

	var (
		updateKeys = map[string]interface{}{
			"last_recharge": lastRecharge,
			"updated_at":    lastRecharge,
		}
		queryKeys = map[string]interface{}{
			"id": uid,
		}
	)
	return db.Update(ctx, "user", updateKeys, queryKeys)
}

func (a *data_User) updateLastLogin(ctx context.Context, uid uint64, lastLogin int64) error {

	var (
		updateKeys = map[string]interface{}{
			"last_login": lastLogin,
			"updated_at": lastLogin,
		}
		queryKeys = map[string]interface{}{
			"id": uid,
		}
	)
	return db.Update(ctx, "user", updateKeys, queryKeys)
}

func (a *data_User) createLoginToken(ctx context.Context, uid uint64, w http.ResponseWriter) (string, error) {
	str, err := a.generateToken(ctx, uid)
	if err != nil {
		return "", err
	}

	c := &http.Cookie{
		Name:     "app" + strconv.Itoa(imapp.AppId()) + "_" + *fCookieName,
		Value:    fmt.Sprintf("%v", uid) + "-" + str,
		HttpOnly: false,
		Path:     *fPathRoot,
		Secure:   *fTlsCert != "" && *fTlsKey != "",
	}
	http.SetCookie(w, c)

	return fmt.Sprintf("%v=%v", c.Name, c.Value), nil
}

func (a *data_User) generateToken(ctx context.Context, uid uint64) (string, error) {
	tk := &loginToken{Uid: uid}
	b, err := json.Marshal(tk)
	if err != nil {
		return "", err
	}
	sKey := rand.RandStr(32)
	if err := a.setSessionKey(ctx, uid, sKey); err != nil {
		return "", err
	}
	str, err := crypto.AesEncrypt(sKey, b)
	return str, err
}

func (a *data_User) setSessionKey(ctx context.Context, uid uint64, sKey string) error {
	sessLock.Lock()
	defer sessLock.Unlock()
	var (
		tableName = "user_auth"
		et        = time.Now().Unix() + int64(*fSessExpire)
	)
	if sess, ok := sessions[uid]; ok {

		updateSQL := fmt.Sprintf("UPDATE `%v` SET `session_key`='%v',`expire_time`=%v WHERE `id`=%v", tableName, sKey, et, sess.ID)
		if _, err := db.ExecRaw(ctx, updateSQL); err != nil {
			return err
		}
		sessions[uid].SessionKey = sKey
		sessions[uid].ExpireTime = et
	} else {

		insertSQL := fmt.Sprintf("INSERT INTO `%v` (`uid`,`session_key`,`expire_time`) VALUES (%v,'%v',%v)", tableName, uid, sKey, et)
		res, err := db.ExecRaw(ctx, insertSQL)
		if err != nil {
			return err
		}
		id, _ := res.LastInsertId()
		sessions[uid] = &UserAuth{
			ID:         uint64(id),
			Uid:        uid,
			SessionKey: sKey,
			ExpireTime: et,
		}
	}
	return nil
}

/*
	server {
		listen 80;
		location / {
			proxy_pass http://127.0.0.1:8181;
			proxy_set_header Host $http_host;
			proxy_set_header X-Real-IP $remote_addr;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
			proxy_set_header X-Forwarded-Proto $scheme;
		}
	}
*/
func (a *data_User) getRemoteAddr(c *server.Context) string {

	addr := strings.Split(c.Http.Request.RemoteAddr, ":")[0]
	if addr == "127.0.0.1" {
		// 可能部署方式是nginx反向代理，这时候从nginx header获取remote addr
		if s := c.Http.Request.Header.Get("X-Real-IP"); s != "" {
			addr = s
		}
	}

	return addr
}

func (a *data_User) loginRecord(ctx context.Context, c *server.Context, userId uint64, account string, name, msg string) interface{} {

	var (
		now  = time.Now().Unix()
		item = map[string]interface{}{
			"creator":       userId,
			"name":          name,
			"login_account": account,
			"login_time":    time.Now().Unix(),
			"msg":           msg,
			"ip":            server.RemoteIP(c.Http.Request),
			"create_at":     now,
			"updated_at":    now,
		}
	)

	_, err := db.Create(ctx, "login_record", item)
	return err
}

func (a *data_User) CreateBusinessRecord(ctx context.Context, userInfo User, logType, logName, logMsg string) error {

	now := time.Now().Unix()
	item := map[string]interface{}{
		"type":          logType,
		"name":          logName,
		"msg":           logMsg,
		"login_account": userInfo.Account,
		"creator":       userInfo.ID,
		"create_at":     now,
		"updated_at":    now,
	}

	_, err := db.Create(ctx, "business_record", item)
	return err
}

func (a *data_User) UpdateBusinessPriceUserCount(ctx context.Context, id uint64, isDel bool) error {

	op := "+"
	var condition string
	if isDel {
		op = "-"

		// 容错处理
		condition = fmt.Sprintf("AND user_count > 0 ")
	}

	var (
		now     = time.Now()
		execSQL = fmt.Sprintf(`
		UPDATE	
			business_price_scheme
		SET 
		    user_count = user_count %v 1,
			updated_at = %v
		WHERE id = %v %v
`, op, now.Unix(), id, condition)
	)

	_, err := db.ExecRaw(ctx, execSQL)
	return err
}

func (a *data_User) updateBizPricesUserCount(ctx context.Context, uid uint64, reqArgs map[string]interface{}, f func() error) error {

	newBizPriceId := reqArgs["business_price_id"].(uint64)
	var oldBizPriceId uint64
	if respUserInfo, _ := a.GetUserInfoByUID(ctx, uid, false); respUserInfo != nil {
		oldBizPriceId = respUserInfo.BusinessPriceId
	}

	if newBizPriceId != oldBizPriceId && newBizPriceId != 0 {
		if err := db.TransactionTx(ctx, func(ctx context.Context, t *mysql.Tx) error {

			if err := a.UpdateBusinessPriceUserCount(ctx, newBizPriceId, false); err != nil {
				return err
			}

			if err := a.UpdateBusinessPriceUserCount(ctx, oldBizPriceId, true); err != nil {
				return err
			}

			if f != nil {
				if err := f(); err != nil {
					return err
				}
			}

			return nil
		}); err != nil {

			log.Err(fmt.Sprintf("更新价格配置用户数量失败:%+v", err))
		}
	}
	return nil
}

func (a *data_User) delExpireSession(args ...interface{}) {

	sessLock.Lock()
	defer sessLock.Unlock()
	now := time.Now().Unix()
	var (
		tableName = "user_auth"
		del       []*UserAuth
	)
	if len(args) > 0 {
		if _, ok := sessions[args[0].(uint64)]; ok {
			del = append(del, &UserAuth{
				Uid: args[0].(uint64),
			})
		}
	} else {

		for _, sess := range sessions {
			if now > sess.ExpireTime {
				del = append(del, sess)
			}
		}
	}
	for _, sess := range del {
		delete(sessions, sess.Uid)

		delSQL := fmt.Sprintf("DELETE FROM `%v` WHERE `uid`=%v", tableName, sess.Uid)
		_, _ = db.ExecRaw(context.Background(), delSQL)
	}
}

func (a *data_User) loadSessions() error {

	var (
		tableName = "user_auth"
		querySQL  = fmt.Sprintf(`SELECT * FROM %v`, tableName)
	)

	resp, err := db.QueryRaw(context.Background(), querySQL, UserAuth{})
	if err != nil {
		log.Err(fmt.Sprintf("load session err %+v", err))
		return err
	}

	for _, v := range resp {
		ua := v.(*UserAuth)
		sessions[ua.Uid] = ua
	}

	return nil
}

func (a *data_User) LoadRole(ctx context.Context, args ...interface{}) (int64, error) {

	var (
		r = adminRole
	)

	if len(args) == 1 {
		if args[0].(int) == 1 {
			r = adminRole
		}
		if args[0].(int) == 2 {
			r = salespersonRole
		}

		if args[0].(int) == 3 {
			r = agentRole
		}

		if args[0].(int) == 4 {
			r = channelAgentRole
		}
	}

	respRole, err := (&data_Role{}).Get(ctx, r.Name, r.Alias)
	if err != nil {
		return r.ID, err
	}

	for _, v := range respRole {
		vRole := v.(*Role)
		if vRole.Name == r.Name {
			r.ID = vRole.ID
			break
		}
	}
	return r.ID, nil
}

// 获取用户信息（非聚合信息）
func (a *data_User) GetUserInfoByUID(ctx context.Context, uid uint64, forUpdate bool) (*User, error) {

	var (
		querySQL = fmt.Sprintf(`
	SELECT * FROM user 
	WHERE id = %v
	AND deleted = 0
`, uid)
	)

	if forUpdate {

		// 表示是否是当前读，用 FOR UPDATE 语句指定
		querySQL += " FOR UPDATE"
	}

	var req []interface{}

	// 如果查询条件附带了 FOR UPDATE，由于查询会立刻返回，所以需要多调用几次，
	if forUpdate {
		var (
			retryCount    = 5
			retryInterval = 100 * time.Millisecond
		)

		// 获取被充值人的账户余额
		for i := 0; i < retryCount; i++ {
			_req, err := db.QueryRaw(ctx, querySQL, User{})
			if err == nil {
				req = _req
				break
			}

			if !isLockError(err) {
				return nil, err
			}

			time.Sleep(retryInterval)
		}
	} else {

		_req, err := db.QueryRaw(ctx, querySQL, User{})
		if err != nil {
			return nil, err
		}
		req = _req
	}

	if req == nil {
		return nil, errors.New(fmt.Sprintf("record not  found"))
	}

	return req[0].(*User), nil
}

func (a *data_User) RoleJudge(req interface{}) interface{} {

	if req != nil {

		for i, v := range req.([]interface{}) {

			resp := v.(*UserQueryRsp)
			req.([]interface{})[i].(*UserQueryRsp).IsAdmin = (&data_Role{}).IsAdminRoleId(uint64(resp.RoleId))
			req.([]interface{})[i].(*UserQueryRsp).IsSalespersonRole = (&data_Role{}).IsSalespersonRole(uint64(resp.RoleId))
			req.([]interface{})[i].(*UserQueryRsp).IsAgentRole = (&data_Role{}).IsAgentRole(uint64(resp.RoleId))
			req.([]interface{})[i].(*UserQueryRsp).IsChannelAgent = (&data_Role{}).IsChannelAgentRole(uint64(resp.RoleId))
		}
	}

	return req
}

func (a *data_User) UpdateUserPoints(ctx context.Context, item map[string]interface{}) error {

	var (
		bp     = item["account_bonus_points"].(uint64)
		cost   = item["will_tp"].(uint64)
		rpCost uint64
		bpCost = cost
	)

	if cost > bp {
		bpCost = bp
		rpCost = cost - bpCost
	}

	var (
		updateSQL = fmt.Sprintf(`
		UPDATE 
		    user 
		SET 
		    recharge_points = recharge_points - %v,	
		    bonus_points = bonus_points - %v
		WHERE
		    id = %v
`, rpCost, bpCost, item["id"])
	)

	_, err := db.ExecRaw(ctx, updateSQL)
	return err
}

// 获取用户信息（聚合信息）
func (a *data_User) GetUserDetail(ctx context.Context, uid uint64) (*UserQueryRsp, error) {

	var (
		querySQL = fmt.Sprintf(`
	SELECT
  user.id AS id,
  user.tag AS tag,
  user.account AS account,
  user.passwd AS passwd,
  user.status AS status,
  user.role_id AS role_id,
  role.name AS role_name,
  role.alias AS role_alias,
  user.leader_id AS leader_id,
  user_leader.account AS leader_name,
  user.recharge_points AS recharge_points,
  user.bonus_points AS bonus_points,
  user.points_consume_account as points_consume_account,
  user.points_consume_gift as points_consume_gift,
  user.create_at AS create_at,
  user.updated_at AS updated_at,
  user.deleted AS deleted
FROM user
  LEFT JOIN role ON role.id = user.role_id AND role.deleted = 0
  LEFT JOIN (
    SELECT
      account,
      id
    FROM user
    WHERE user.deleted = 0 GROUP BY id,account) user_leader
  ON user_leader.id = user.leader_id
WHERE user.deleted = 0
AND user.id = %v
ORDER BY user.create_at DESC
`, uid)
	)

	req, err := db.QueryRaw(ctx, querySQL, UserQueryRsp{})
	if err != nil {
		return nil, err
	}

	if req == nil {
		return nil, errors.New(fmt.Sprintf("record not  found"))
	}

	userDetail := req[0].(*UserQueryRsp)
	userDetail.IsAdmin = (&data_Role{}).IsAdminRoleId(uint64(userDetail.RoleId))
	userDetail.IsSalespersonRole = (&data_Role{}).IsSalespersonRole(uint64(userDetail.RoleId))
	userDetail.IsAgentRole = (&data_Role{}).IsAgentRole(uint64(userDetail.RoleId))
	userDetail.IsChannelAgent = (&data_Role{}).IsChannelAgentRole(uint64(userDetail.RoleId))
	return userDetail, nil
}

type loginToken struct {
	Uid uint64
}

func GetLoginUid(r *http.Request) int {

	tk := (&data_User{}).getLoginToken(r)
	if tk == nil {
		return 0
	}
	return int(tk.Uid)
}

func (a *data_User) getLoginToken(r *http.Request) *loginToken {
	token := ""
	if ck, err := r.Cookie("app" + strconv.Itoa(imapp.AppId()) + "_" + *fCookieName); err == nil {
		token = ck.Value
	}
	if *fDebugMode {
		if token == "" {
			token = r.PostForm.Get("_t")
		}
		if token == "" {
			token = r.URL.Query().Get("_t")
		}
	}
	if token == "" {
		return nil
	}
	ss := strings.SplitN(token, "-", 2)
	uid, err := strconv.Atoi(ss[0])
	if err != nil {
		return nil
	}
	if *fDebugMode && *fDebugSession != "" && ss[1] == *fDebugSession {
		return &loginToken{Uid: uint64(uid)}
	}
	skey := a.getSessionKey(uid)
	if skey == "" {
		return nil
	}
	b, err := crypto.AesDecrypt(skey, ss[1])
	if err != nil {
		return nil
	}
	tk := &loginToken{}
	if err := json.Unmarshal(b, &tk); err != nil {
		return nil
	}
	if uint64(uid) != tk.Uid {
		return nil
	}
	return tk
}

func (a *data_User) getSessionKey(uid int, args ...interface{}) string {
	sessLock.Lock()
	defer sessLock.Unlock()
	sess, ok := sessions[uint64(uid)]
	if !ok {
		return ""
	}

	var (
		tableName = "user_auth"
	)

	if len(args) == 1 && args[0].(bool) == false {
		return sess.SessionKey
	}

	sess.ExpireTime = time.Now().Unix() + int64(*fSessExpire)
	updateSQL := fmt.Sprintf("UPDATE `%v` SET `expire_time`=%v WHERE `id`=%v", tableName, sess.ExpireTime, sess.ID)
	if _, err := db.ExecRaw(context.Background(), updateSQL); err != nil {
		return ""
	}

	return sess.SessionKey
}
